﻿
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NSQLObjects.Filter;
using NSQLObjects.Persistence;
using NSQLObjectsTests.DBModel;

namespace NSQLObjectsTests
{
    [TestClass]
    public class OrderTests
    {
        [ClassInitialize()]
        public static void OrderTestsInitialize(TestContext testContext) {
            DefaultSetup.Connect();
        }

        [TestMethod]
        public void CreateOrder() {
            DefaultSetup.Connect();
            Random rnd = new Random();
            long newOrderID;
            int rows;
            using (var ctx = SqlDataContext.CreateContext()) {
                AllCustomers ac = new AllCustomers();
                ProductCount pc = new ProductCount();
                ac.LoadRange(ctx, 0, 100);
                pc.Load(ctx);
                if (ac.Count == 0 || pc.Count == 0) {
                    Assert.Inconclusive("Run test again after some customers and products have been created");
                    return;
                }
                Order order = new Order();
                order.CustomerID = ac[rnd.Next(ac.Count)].ID;
                order.OrderDate = DateTime.Now;
                ctx.Insert(order);
                rows = rnd.Next(5) + 2;
                for (short i = 1; i <= rows; i++) {
                    OrderLineRow line = new OrderLineRow();
                    line.LineNro = i;
                    line.ProductID = 1 + rnd.Next(pc.Count);
                    line.Amount = (float)Math.Round((5 * rnd.NextDouble() + 1.0) * (5 * rnd.NextDouble() + 1.0), 1);
                    // Assign identity to children before they are inserted to database
                    order.SetParentID += newID => line.OrderID = newID;
                    ctx.Insert(line);
                }
                ctx.SubmitChanges();    // ID will be created on insert
                newOrderID = order.ID;
            }
            using (var ctx = SqlDataContext.CreateContext()) {
                AllOrders orderQuery = new AllOrders();
                orderQuery.IsEQ(ol => ol.ID, newOrderID);
                var order = orderQuery.LoadSingle(ctx, false);
                Assert.IsNotNull(order);
                // Assume that server and client clock are within 0.5 hours
                Assert.AreEqual(0.0, Math.Round((DateTime.Now - order.OrderDate).TotalHours));
                Assert.AreEqual(rows, order.Lines.Value.Count);
                double lineSum = order.Lines.Value.Sum(ol => (double)ol.Amount * (double)ol.UnitPrice);
                if (Math.Abs(lineSum - order.TotalAmount) > 0.1)
                    Assert.Fail("Total line price calculation failed");
            }
        }

        [TestMethod()]
        public void DynamicSorting() {
            using (var ctx = SqlDataContext.CreateContext()) {
                AllOrders orderQuery = new AllOrders();
                Assert.IsTrue(orderQuery.CanSortBy(row => row.Name));
                orderQuery.SortDescBy(row => row.Name);
                orderQuery.LoadRange(ctx, 0, 10);
                Assert.IsTrue(CheckInOrder(orderQuery, (r1, r2) => String.Compare(r1.Name, r2.Name) >= 0));
                orderQuery.Clear();
                orderQuery.ClearSorting();
                orderQuery.SortBy(row => row.Name);
                orderQuery.LoadRange(ctx, 0, 10);
                Assert.IsTrue(CheckInOrder(orderQuery, (r1, r2) => String.Compare(r1.Name, r2.Name) <= 0));
            }
        }

        [TestMethod()]
        public void ComputedUpdateTest() {
            long id;
            using (var ctx = SqlDataContext.CreateContext()) {
                DispachedOrder dp = new DispachedOrder();
                dp.Load(ctx);
                if (!dp.IsLoaded) {
                    CreateOrder();
                    dp.Load(ctx);
                }
                Assert.IsTrue(dp.IsLoaded);
                dp.DispatchDate = DateTime.Now;
                id = dp.ID;
                ctx.SubmitChanges();
            }
            using (var ctx = SqlDataContext.CreateContext()) {
                AllOrders orders = new AllOrders();
                orders.IsEQ(r => r.ID, id);
                var order = orders.LoadSingle(ctx, false);
                Assert.IsNotNull(order);
                // Assume that server and client clock are within 0.5 hours
                Assert.AreEqual(0.0, Math.Round((DateTime.Now - order.ModifiedDate).TotalHours));
            }
        }

        /// <summary>
        /// Helper to fill some test data, not an actual test
        /// </summary>
        // [TestMethod()]
        public void FillTestData() {
            int count;
            using (var ctx = SqlDataContext.CreateContext()) {
                AllOrders orderQuery = new AllOrders();
                count = orderQuery.QueryCount(ctx);
            }
            while (count < 1000) {
                CreateOrder();
                count++;
            }
        }

        private bool CheckInOrder<T>(IEnumerable<T> list, Func<T, T, bool> compare) where T : class {
            T current = null;
            foreach (T tmp in list) {
                if (current == null)
                    current = tmp;
                else {
                    if (!compare(current, tmp))
                        return false;
                    current = tmp;
                }
            }
            return true;
        }
    }

}

namespace NSQLObjectsTests.DBModel
{
    partial class Order : IEntityCreated, ITableRowCommit
    {
        public event Action<long> SetParentID;

        public void SetIdentity(long newIdentity) {
            ID = newIdentity;
            if (SetParentID != null)
                SetParentID(newIdentity);
        }

        public void BeforeDatabaseChanges(RowStates state, NSQLObjects.Model.ITableRow originalValues, ICommitContext context) {

        }

        public void AfterDatabaseChanges(RowStates state, NSQLObjects.Model.ITableRow originalValues, ICommitContext context) {
            ParameterList pl = new ParameterList();
            pl.Create("@ID").ToSql(ID);
            context.ExecuteProcedure("dbo.CountOrderTotal", pl);
        }
    }

    partial class OrderRow : IRowLoaded
    {
        public Lazy<OrderLine> Lines { get; private set; }

        public void Loaded(SqlDataContext context) {
            Lines = new Lazy<OrderLine>(() => context.Load<OrderLine>(s => s.OrderID = ID));

        }
    }
}
